feat: add paid, userEmail, userName, and rating filters to insights page#23299
feat: add paid, userEmail, userName, and rating filters to insights page#23299Udit-takkar merged 9 commits intomainfrom
Conversation
- Add paid filter as MULTI_SELECT with 'Paid'/'Free' options - Add userEmail filter as TEXT for searching user emails - Add userName filter as TEXT for searching user names - Update DummyTableRow type to include new filter columns - Add backend filtering logic in buildColumnFilterCondition method - Use existing columns from BookingTimeStatusDenormalized view - Follow established patterns from /bookings page implementation Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
WalkthroughAdds i18n support for a new Possibly related PRs
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
- Updated useInsightsBookings.ts to use ColumnFilterType.SINGLE_SELECT for paid filter - Modified buildColumnFilterCondition to handle single select with isSingleSelectFilterValue - Simplified backend logic to handle single boolean value instead of array Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Graphite Automations"Add consumer team as reviewer" took an action on this PR • (08/24/25)1 reviewer was added to this PR based on Keith Williams's automation. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/lib/server/service/InsightsBookingBaseService.ts (1)
331-337: Trim operands and optionally escape wildcard characters in ILIKE filters.Functionally OK and parameterized (safe). For better UX and predictability:
- Trim operands; ignore empty strings to avoid adding no-op conditions.
- Optionally escape
%and_if you intend a literal substring match rather than allowing user-supplied wildcards.Apply minimal trimming (keeps current wildcard behavior):
- if (id === "userEmail" && isTextFilterValue(value)) { - return Prisma.sql`"userEmail" ILIKE ${`%${value.data.operand}%`}`; - } + if (id === "userEmail" && isTextFilterValue(value)) { + const operand = value.data.operand.trim(); + if (!operand) return null; + return Prisma.sql`"userEmail" ILIKE '%' || ${operand} || '%'`; + } @@ - if (id === "userName" && isTextFilterValue(value)) { - return Prisma.sql`"userName" ILIKE ${`%${value.data.operand}%`}`; - } + if (id === "userName" && isTextFilterValue(value)) { + const operand = value.data.operand.trim(); + if (!operand) return null; + return Prisma.sql`"userName" ILIKE '%' || ${operand} || '%'`; + }If you prefer strict literal substring matching, I can provide a small
escapeLikeForIlike()helper and addESCAPE '\\'.On large datasets, consider trigram GIN indexes to keep these ILIKE scans fast:
- CREATE EXTENSION IF NOT EXISTS pg_trgm;
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_bts_useremail_trgm ON "BookingTimeStatusDenormalized" USING gin ("userEmail" gin_trgm_ops);
- Same for "userName".
packages/features/insights/hooks/useInsightsBookings.ts (1)
86-111: Text filter columns are consistent; consider a tiny helper to avoid duplication.Both columns share identical config except id/header/accessor/size. Optional: extract a small factory to reduce repetition.
Example helper (outside this block) and replacement use:
// helper near top of file const textFilterCol = <K extends keyof DummyTableRow>(key: K, id: string, header: string, size = 200) => columnHelper.accessor(key, { id, header, size, meta: { filter: { type: ColumnFilterType.TEXT } }, enableColumnFilter: true, enableSorting: false, cell: () => null, }); // inside columns: textFilterCol("userEmail", "userEmail", t("user_email")); textFilterCol("userName", "userName", t("user_name"));
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts(3 hunks)packages/features/insights/hooks/useInsightsBookings.ts(2 hunks)packages/lib/server/service/InsightsBookingBaseService.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*Service.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Service files must include
Servicesuffix, use PascalCase matching exported class, and avoid generic names (e.g.,MembershipService.ts)
Files:
packages/lib/server/service/InsightsBookingBaseService.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/lib/server/service/InsightsBookingBaseService.tspackages/features/insights/hooks/useInsightsBookings.tspackages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/lib/server/service/InsightsBookingBaseService.tspackages/features/insights/hooks/useInsightsBookings.tspackages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/lib/server/service/InsightsBookingBaseService.tspackages/features/insights/hooks/useInsightsBookings.tspackages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/RoutingFunnel.tsx:15-17
Timestamp: 2025-07-15T12:58:40.539Z
Learning: In the insights routing funnel component (packages/features/insights/components/RoutingFunnel.tsx), the useColumnFilters exclusions are intentionally different from the general useInsightsParameters exclusions. RoutingFunnel specifically excludes only ["createdAt"] while useInsightsParameters excludes ["bookingUserId", "formId", "createdAt", "eventTypeId"]. This difference is by design.
📚 Learning: 2025-07-24T08:39:06.185Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22702
File: packages/lib/server/service/insightsBooking.ts:120-124
Timestamp: 2025-07-24T08:39:06.185Z
Learning: In the InsightsBookingService (packages/lib/server/service/insightsBooking.ts), the constructor stores null for invalid options or filters but this is handled safely through null checks in buildFilterConditions() and buildAuthorizationConditions() methods. The service uses defensive programming to return safe fallback conditions (null or NOTHING_CONDITION) rather than throwing errors on invalid inputs.
Applied to files:
packages/lib/server/service/InsightsBookingBaseService.ts
📚 Learning: 2025-07-15T13:02:17.403Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/lib/server/service/insightsRouting.ts:367-368
Timestamp: 2025-07-15T13:02:17.403Z
Learning: In the InsightsRoutingService (packages/lib/server/service/insightsRouting.ts), multi-select filter data is already validated by zod before reaching the buildFormFieldSqlCondition method, so null/undefined values are not present in filterValue.data arrays and don't need to be filtered out.
Applied to files:
packages/lib/server/service/InsightsBookingBaseService.ts
📚 Learning: 2025-08-22T16:38:00.182Z
Learnt from: bandhan-majumder
PR: calcom/cal.com#23192
File: packages/lib/server/service/InsightsBookingBaseService.ts:814-816
Timestamp: 2025-08-22T16:38:00.182Z
Learning: In the InsightsBookingBaseService (packages/lib/server/service/InsightsBookingBaseService.ts), when filtering for "accepted" bookings in getMembersStatsWithCount(), using `endTime <= now()` in the SQL condition should be avoided as it conflicts with existing date filtering logic. The components already handle completion filtering by setting `endDate: currentTime` in their query parameters, making additional SQL-level endTime filtering unnecessary and potentially problematic.
Applied to files:
packages/lib/server/service/InsightsBookingBaseService.ts
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In BookingPaymentInitiatedDTO and other webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking field is a restricted structure containing only specific fields (id, eventTypeId, userId) rather than the full database booking object, so there are no security or PII leakage concerns when passing the booking object to buildEventPayload.
Applied to files:
packages/features/insights/hooks/useInsightsBookings.ts
🧬 Code graph analysis (2)
packages/lib/server/service/InsightsBookingBaseService.ts (1)
packages/features/data-table/lib/utils.ts (2)
isSingleSelectFilterValue(86-88)isTextFilterValue(49-51)
packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts (2)
packages/features/data-table/lib/utils.ts (1)
convertFacetedValuesToMap(150-154)packages/features/data-table/lib/types.ts (1)
FacetedValue(222-226)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
- GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (6)
packages/lib/server/service/InsightsBookingBaseService.ts (2)
8-12: Importing isTextFilterValue improves type-safety for text filters.This aligns with the backend filter additions and prevents unsafe access to value.data for TEXT filters.
326-329: Confirmed UI sends “true”/“false” strings for the paid filter
The frontend hook at packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts defines the paid column’s options as{ value: "true" }and{ value: "false" }, which aligns with the backend coercion in InsightsBookingBaseService.ts (lines 326–329). No changes are needed.packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts (2)
73-77: Paid facet values and labels match backend expectations.Values "true"/"false" align with the service’s coercion. Order (Paid, Free) is sensible.
81-81: Dependency array correctly includes t.Prevents stale labels when locale changes.
packages/features/insights/hooks/useInsightsBookings.ts (2)
15-17: DummyTableRow extension is consistent with backend schema.Types for paid, userEmail, and userName align with BookingTimeStatusDenormalized.
73-85: "Paid" column wiring is correct.
- SINGLE_SELECT matches the faceted map in useInsightsBookingFacetedUniqueValues.
- Sorting disabled is appropriate for a boolean-as-select column.
| import { useCallback } from "react"; | ||
|
|
||
| import { convertFacetedValuesToMap, type FacetedValue } from "@calcom/features/data-table"; | ||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
i18n hookup looks good; ensure keys exist.
Importing useLocale and using t is correct. Please verify that "paid" and "free" keys exist in locale JSONs.
Also applies to: 28-28
🏁 Script executed:
#!/bin/bash
# Verify i18n keys exist somewhere in locale JSON files
rg -nP '"(paid|free|user_email|user_name)"\s*:' -g '!**/node_modules/**' -SLength of output: 3059
Add missing i18n keys for “free”, “user_email”, and “user_name”
The lookup for “paid” succeeded across all locale JSONs, but the keys “free”, “user_email”, and “user_name” were not found. Please add these entries to each common.json (and any other relevant namespace) under apps/web/public/static/locales/*/. For example, in apps/web/public/static/locales/en/common.json:
{
"paid": "Paid",
+ "free": "Free",
+ "user_email": "User email",
+ "user_name": "User name",
…
}– Repeat for every locale to ensure complete coverage.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
around line 5, the code references i18n keys "free", "user_email", and
"user_name" that are missing; add these keys to every locale file (e.g.,
apps/web/public/static/locales/*/common.json and any other used namespace) with
appropriate translations (matching structure of existing keys like "paid"),
ensure JSON syntax is valid, and run the i18n/locales validation or app build to
confirm no missing-key errors remain.
There was a problem hiding this comment.
@eunjae-lee this is correct. user_email, user_name doesn't exist in common.json
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
@Udit-takkar thanks for pointing it out ! just added a commit for that
E2E results are ready! |
- Add rating field to DummyTableRow type - Add rating column with SINGLE_SELECT filter type - Add rating options 1-5 in faceted unique values - Add rating filtering logic in backend service - Rating filter allows filtering bookings by 1-5 star ratings Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts (1)
73-77: Ensure “free” i18n key exists across locales.The UI labels t("paid") and t("free") are correct, but prior checks showed “free” missing in several locale JSONs. Add/verify "free" in all locales to avoid runtime fallbacks.
Run to confirm coverage:
#!/bin/bash # Verify presence of keys in locale files rg -nP '"(paid|free)"\s*:' apps/web/public/static/locales -S -g '!**/node_modules/**'packages/features/insights/hooks/useInsightsBookings.ts (1)
100-112: i18n keys for “user_email” and “user_name”.Headers use t("user_email") and t("user_name"). Prior scans indicated these keys were missing in some locales. Add them across locales to avoid untranslated labels.
Check coverage:
#!/bin/bash rg -nP '"(user_email|user_name)"\s*:' apps/web/public/static/locales -S -g '!**/node_modules/**'
🧹 Nitpick comments (3)
packages/features/insights/hooks/useInsightsBookings.ts (3)
15-18: Double-check nullability for “paid”; align with data source.If BookingTimeStatusDenormalized.paid can be null (unknown/NA), consider typing as boolean | null to avoid accidental narrowing. If it’s guaranteed non-null in the denormalized view, ignore.
Optional type tweak:
- paid: boolean; + paid: boolean | null;
113-125: Confirm “rating” column belongs in this PR; ensure end-to-end support.This adds a rating filter/column not mentioned in the PR objectives. Confirm product intent, ensure i18n for t("rating") exists, and verify the backend implements columnId === "rating" handling.
If keeping it, consider constraining the domain in types:
- rating: number | null; + rating: 1 | 2 | 3 | 4 | 5 | null;
33-36: Reduce duplication of cell: () => null and filter meta boilerplate.Consider a small helper to DRY column creation for “filter-only” columns (no cell renderer, sorting disabled), improving readability and consistency.
Example helper (outside this diff scope):
const filterOnly = <T,>(c: ColumnDef<T>) => ({ enableSorting: false, cell: () => null, ...c });Then wrap each column def with filterOnly({...}).
Also applies to: 62-73, 74-126
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts(3 hunks)packages/features/insights/hooks/useInsightsBookings.ts(2 hunks)packages/lib/server/service/InsightsBookingBaseService.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/lib/server/service/InsightsBookingBaseService.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/features/insights/hooks/useInsightsBookings.tspackages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/features/insights/hooks/useInsightsBookings.tspackages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/features/insights/hooks/useInsightsBookings.tspackages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
🧬 Code graph analysis (1)
packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts (2)
packages/features/data-table/lib/utils.ts (1)
convertFacetedValuesToMap(150-154)packages/features/data-table/lib/types.ts (1)
FacetedValue(222-226)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (3)
packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts (1)
5-5: Locale hookup and dependency coverage look correct.Good use of useLocale and inclusion of t in the useCallback deps to ensure labels react to locale changes.
Also applies to: 28-28, 89-89
packages/features/insights/hooks/useInsightsBookings.ts (2)
74-86: Frontend ‘paid’ column wiring confirmed; please verify backend alignment
- In useInsightsBookings.ts, the
paidaccessor is correctly set up with SINGLE_SELECT andcell: () => null.- In useInsightsBookingFacetedUniqueValues.ts (line 73), there is a branch for
columnId === "paid"that maps faceted values{ value: "true", label: t("paid") }.Ensure that the backend schema or API for bookings exposes a field named
"paid"so that this columnId aligns end-to-end and avoids silent mismatches.
87-99: PII Filter Safety: Verify Server-Side Access Controls foruserEmailExposing a free-text
userEmailfilter in the UI means users can query bookings by arbitrary email strings. Before shipping, please confirm on the server that everyviewer.insights.*endpoint:• Authenticates the caller (e.g. via TRPC’s session)
• Restricts returned records to the caller’s own team/org context (e.g.WHERE teamId = session.teamIdororgId = session.orgId)
• Applies these scoping rules before any text filter on email is appliedWithout explicit tenant scoping in the resolver, a malicious user could craft a filter that leaks PII across org boundaries.
Action items:
- Review each resolver in your
viewer.insightsTRPC router (e.g. the handler for bookings queries such asbookingsByHourStats,membersWithMostBookings, etc.).- Ensure the incoming session’s
teamId/orgIdis enforced in the database query before injecting anyWHERE email LIKE …clause.- Add automated tests that attempt to fetch another tenant’s email and assert it returns no results.
packages/features/insights/hooks/useInsightsBookingFacetedUniqueValues.ts
Outdated
Show resolved
Hide resolved
- Update rating filter from SINGLE_SELECT to NUMBER in useInsightsBookings.ts - Remove rating options from useInsightsBookingFacetedUniqueValues.ts since NUMBER filters don't need predefined options - Add isNumberFilterValue import and implement number filter logic in InsightsBookingBaseService.ts - Support eq, gt, gte, lt, lte operators for rating number filter Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
- Import makeSqlCondition from @calcom/features/data-table/lib/server - Replace manual SQL condition building with makeSqlCondition utility for userEmail, userName, and rating filters - Maintain existing paid filter logic for boolean conversion - Follow established pattern from InsightsRoutingBaseService - Cleaner, more maintainable code with consistent filter handling Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Udit-takkar
left a comment
There was a problem hiding this comment.
Left one comment #23299 (comment)
What does this PR do?
This PR adds three new filter types to the
/insightspage to enhance booking data analysis capabilities:paid- SINGLE_SELECT filter with "Paid"/"Free" options to distinguish between paid and free bookingsuserEmail- TEXT filter for searching bookings by user email addressesuserName- TEXT filter for searching bookings by user namesThe implementation follows established patterns from the
/bookingspage and leverages existing columns from theBookingTimeStatusDenormalizedview.Link to Devin run: https://app.devin.ai/sessions/e4288a28d13247e9af7ae9a8767d17b7
Requested by: @eunjae-lee
Technical Changes
Frontend Changes
useInsightsBookings.tsto add three new column definitions with appropriate filter typesDummyTableRowtype to include the new filter fieldsuseInsightsBookingFacetedUniqueValues.tsto provide "Paid"/"Free" options for the paid filterBackend Changes
InsightsBookingBaseService.tsbuildColumnFilterConditionmethodisTextFilterValueimport for text filter supportIMPORTANT: Due to authentication issues in the local dev environment, end-to-end UI testing could not be completed. Manual testing of the filter functionality is essential before merging.
High-priority review areas:
🔴 Manual UI Testing Required:
/insightspage and verify all three filters appear correctly🔴 Translation Keys Verification:
paid,free,user_email,user_namekeys exist in the i18n system🔴 Data Availability:
userEmailanduserNamecolumns are properly populated in theBookingTimeStatusDenormalizeddatabase viewpaidcolumn contains boolean values as expected🔴 Type Safety:
useInsightsBookingFacetedUniqueValues.tsbuildColumnFilterConditionmethod handles thepaidfilter type conversion correctly🔴 SQL Query Construction:
buildColumnFilterConditionis secure and correctHow should this be tested?
Prerequisites
paid,userEmail, anduserNamefields/insightspage (requires appropriate permissions)Test Cases
1. Paid Filter (SINGLE_SELECT)
/insightspage2. User Email Filter (TEXT)
3. User Name Filter (TEXT)
4. Integration Testing
Expected Behavior
Mandatory Tasks (DO NOT REMOVE)
Checklist